# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
Contains a standard interface used by a mapper

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


import sys

# GTK+ and friends
import pygtk
if not sys.platform == 'win32':
    pygtk.require('2.0')
try:
    import gtk
except:
    sys.exit(1)
    
# Project imports
from CMap2Prolog import assemblyQuery, assemblyText
from pyswip.prolog import PrologError 
from question import Question
from log import log, statLog
log.debug('Loaded...')


def getEquivalentElements(db, setofElements):
    '''Will return a set of all the elements that are equivalent to that one'''
    query = 'equals(%s, NameSet)'
    equivalentElements = set()
    
    for element in setofElements:
        equivalentElements.add(element)
        filledQuery = assemblyQuery(query, element)
        result = list(db.query(filledQuery))
        for nameSet in result:
            for otherName in nameSet['NameSet']:
                equivalentElements.add(otherName)
    
    return equivalentElements


def getLabelOfElements(db, setofElements, contract=False, contractLength = 10):
    '''Will return a set of labels for the informed elements'''
    
    query = 'label(%s, Label)'
    elementLabels = []
    
    for element in setofElements:
        filledQuery = assemblyQuery(query, element)
        result = list(db.query(filledQuery))
        for nameSet in result:
            elementLabels.append(nameSet['Label'])
    
    return elementLabels
        

def clickBehaviour(func):
    '''Sets the standard behavior a button should have when clicked'''
    def wrapper(self, widget = None):
        "The function returned"
        widget.set_relief(gtk.RELIEF_NONE)
        res = func(self, widget)
        return res
    
    return wrapper


class MapperQuestion(Question):
    '''A question that adds a useful fact when Yes or No is clicked'''
    
    def __init__(self,
                 db,
                 data,
                 question = '',
                 query = '',
                 helpFile = 'basic',):

        Question.__init__(self)
        
        self.db = db
        self.data = data
        
        self.questionTemplate = question
        
        self.queryTemplate = query
        self.queryData = ''
        self.queryIds = ''
        
        self.ids = set()
        self.toHighlight = set()
        
        self.asserted = []
        self.undoBox = None
        self.buttonPressed = False
        self.sensitive = True      # Used to get statistics
        
        self.helpFile = helpFile
        
        self.responseLogger = None
        
    def createData(self):
        '''Return the label that will be used (top part of the question)'''
        self.updateQueryData()
        self.updateQueryIds()
        self.updateIds()
    
    def createLabel(self):
        '''Return the label that will be used (top part of the question)'''
        label = gtk.Label('')
        label.set_line_wrap(True)
        label.set_use_markup(True)
        label.set_property('width-request', 300)
        
        return label
    
    def updateLabel(self):
        '''Updates the label that will be shown by the boxes'''
        label = assemblyText(self.questionTemplate, self.data, self.db)
        if label != None:
            self.label.set_label(label)
    
    def createButtons(self):
        '''Returns a list with the buttons that will be used'''
        bYes = gtk.Button('Yes')
        bYes.connect('clicked', self.yes)
        bYes.connect('button-press-event', self.onButtonPress)
        
        bNo = gtk.Button('No')
        bNo.connect('clicked', self.no)
        bNo.connect('button-press-event', self.onButtonPress)
        
        return [bYes, bNo]
    
    def createMenu(self):
        '''Returns a popup menu to be attached to the event box'''
        
        menu = gtk.Menu()

        helpItem = gtk.MenuItem('Help')
        helpItem.connect('activate', self.showHelp)
        menu.append(helpItem)
        
        center = gtk.MenuItem('Center highlighted')
        center.connect('activate', self.centerOnHighlight)
        menu.append(center)
        
        menu.connect('enter-notify-event', self.onEnter)
        menu.connect('leave-notify-event', self.onLeave)
        menu.show_all()
   
        return menu
    
    def onEnter(self, widget = None, event = None):
        '''Focus on the cmap the necessary elements'''
        
        if None != self.owner:
            self.owner.focus = self
        return False
    
    def onLeave(self, widget = None, event = None):
        '''Unfocus on the cmap the necessary elements'''
      
        if None != self.owner:
            self.owner.focus = []
        return False
    
    @clickBehaviour
    def yes(self, widget = None):
        '''Called when the user chooses yes'''
        queries = self.assertOnYes()
        statLog.info('Suggestion accepted: %s' % queries)
        self.assertz(queries)

    @clickBehaviour
    def no(self, widget = None):
        '''Called when the user chooses no'''
        queries = self.assertOnNo()
        statLog.info('Suggestion rejected: %s' % queries)
        self.assertz(queries)
    
    def updateData(self):
        '''Updates the info that this class uses'''
        self.updateQueryData()
        self.updateQueryIds()
        self.updateIds()
        self.updateHighlights()
    
    def updateQueryData(self):
        '''Creates a string representing a query'''
        self.queryData = 'possible(%s)' % assemblyQuery(self.queryTemplate,
                                                        self.data)
    
    def updateQueryIds(self):
        '''Creates a string representing a query'''
        self.queryIds = self.queryData
    
    def updateIds(self):
        '''Updates the elements that should be highlighted in the cmap. Must
           always be a set'''
        
        self.ids = set(self.data['Ids'])
    
    def updateHighlights(self):
        '''Update the elements that should be highlighted in the map'''
        if len(self.ids) > 0:
            self.toHighlight = getEquivalentElements(self.db, self.ids)
      
    def updateSensitivity(self):
        '''Recalculates the status when the question was changed'''
        
        if not self.buttonPressed:
            answers = self.db.query(self.queryData)
            try:
                answers.next()
            except (KeyError, StopIteration):
                # Make the three itens insensitive
                self.setAsInsensitive()
            except PrologError:
                log.error('PrologError in question query: %s' % self.queryData)
                pass
            else:
                self.setAsSensitive()
        else:
            self.setAsInsensitive()
    
    def setAsSensitive(self):
        '''Set the question as a sensitive'''
        # Make the three itens sensitive
        if self.label != None:
            self.label.set_sensitive(True)
        if self.middle != None:
            self.middle.set_sensitive(True)
        if self.buttons != None:
            for button in self.buttons:
                button.set_sensitive(True)
                button.set_relief(gtk.RELIEF_NORMAL)
        self.sensitive = True
    
    def setAsInsensitive(self):
        '''Set the question as a insensitive'''
        # Make the three itens insensitive
        if self.label != None:
            self.label.set_sensitive(False)
        if self.middle != None:
            self.middle.set_sensitive(False)
        if self.buttons != None:
            for button in self.buttons:
                button.set_sensitive(False)
        self.sensitive = False
        
    def assertOnYes(self):
        '''Creates a string representing a question'''
        return ['isTrue(%s)' % assemblyQuery(self.queryTemplate, self.data)]
    
    def assertOnNo(self):
        '''Creates a string representing a question'''
        return ['isFalse(%s)' % assemblyQuery(self.queryTemplate, self.data)]
    
    def __eq__(self, element):
        if type(self) != type(element):
            return False
        
        return self.queryTemplate == element.queryTemplate and \
               self.ids == element.ids
    
    def assertz(self, queries):
        '''Assert some queries'''
        for query in queries:
            self.db.assertz(query)
            if self.responseLogger != None:
                self.responseLogger.write('assertz(%s)\n' % query)
                self.responseLogger.flush()
        self.switchToUndo(queries)
        self.changed()
        
    def asserta(self, queries):
        '''Assert some queries'''
        for query in queries:
            self.db.asserta(query)
            if self.responseLogger != None:
                self.responseLogger.write('asserta(%s)\n' % query)
                self.responseLogger.flush()
        self.switchToUndo(queries)
        self.changed()
        
    def retract(self, queries):
        '''Assert some queries'''
        for query in queries:
            self.db.retract(query)
            if self.responseLogger != None:
                self.responseLogger.write('retract(%s)\n' % query)
                self.responseLogger.flush()
        self.changed(True)
        
    def changed(self, undo = False):
        '''Emits the signal on-answer'''
        self.emit('changed', undo)

    def switchToUndo(self, asserted):
        '''When one answer is made, switch to the undo state, where the user is
        allowed to change idea'''
        
        # Flag that a button was pressed
        self.buttonPressed = True
                
        # Save the options so it can be retracted
        self.asserted = asserted
        
        # Now add an undo button
        self.undoBox = gtk.Button('Undo')
        self.undoBox.connect('clicked', self.undo)
        self.buttonBar.pack_end(self.undoBox, expand=False)
        
    def undo(self, widget = None):
        '''Undo the action last done'''
        # Flag that a button was pressed
        self.buttonPressed = False
        
        self.buttonBar.remove(self.undoBox)
        for button in self.buttons:
            button.set_relief(gtk.RELIEF_NORMAL)
        
        # Retract will set a update cycle
        self.retract(self.asserted)
        
        statLog.info('Answer undone: %s' % self.asserted)
        
    def showHelp(self, menuItem = None):
        '''Show a help message related to the question'''
        self.owner.help.show(self.helpFile)
        
    def centerOnHighlight(self, *v):
        '''Set the canvas centered on the highlighted elements.'''
        
        self.owner.centerOnHighlight()
        
        
